package controls;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Set;

import model.AbstractModel;

import utilities.IDGenerator;
import utilities.Key;
import view.ViewModule;

import com.jme3.audio.AudioNode;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;

import eventManager.AppEvent;
import eventManager.EventPortal;
import eventManager.IEventListener;

/**
 * Primary GameControl.  Contains references to object model data, all spatials
 * for object, all other GameControls for these spatials.  Also handles event
 * messaging to other objects in the game via IEventListener interface
 * @author RJ
 *
 */
public abstract class AbstractMasterControl implements IMasterControl {
	
	protected Key key;
	protected HashMap<String, Geometry> geometries = new HashMap<String, Geometry>();
	protected HashMap<String, Node> nodes = new HashMap<String, Node>();
	protected HashMap<String, AudioNode> audioNodes = new HashMap<String, AudioNode>();
	
	public HashMap<String, GameControl> gameControls = new HashMap<String, GameControl>();

	protected boolean enabled = true;
    protected Spatial spatial; //current spatial that this MasterControl is attached to.
    protected String name;    	
	
	//Contain all events the MasterControl listens for, mapped by destination
	protected HashMap<String, IEventListener> eventMap = new HashMap<String, IEventListener>();
	
	public AbstractMasterControl(ViewModule views) {
		//assign unique ID
		IDGenerator id = IDGenerator.getInstance();
		this.key = id.newID();
		this.name = "MasterControl";
		
		ViewModule module;
		String name;
		ArrayList<Node> currNodes;
		ArrayList<AudioNode> currAudioNodes;
		ArrayList<Geometry> currGeometries;		
			
		currNodes = views.getNodes();
		currAudioNodes = views.getAudioNodes();
		currGeometries = views.getGeometries();

		for (int j = 0; j < currNodes.size(); j++) {
			name = currNodes.get(j).getName();
			nodes.put(name, currNodes.get(j));
		}

		for (int j = 0; j < currAudioNodes.size(); j++) {
			name = currAudioNodes.get(j).getName();
			audioNodes.put(name, currAudioNodes.get(j));
		}

		for (int j = 0; j < currGeometries.size(); j++) {
			name = currGeometries.get(j).getName();
			geometries.put(name, currGeometries.get(j));
		}
		
	}
	
	public Key getKey() {
		return this.key;
	}
	
	public int getKeyValue() {
		return key.getValue();
	}
	
	
	public void addGeometry(Geometry geometry) {
		this.geometries.put(geometry.getName(), geometry);
	}
	
	public void addNode(Node node) {
		this.nodes.put(node.getName(), node);
	}
	
	public void addAudioNode(AudioNode audioNode) {
		this.audioNodes.put(audioNode.getName(), audioNode);
	}
	
	public void removeGeometry(String name) {
		this.geometries.remove(name);
	}
	
	public void removeNode(String name) {
		this.nodes.remove(name);
	}
	
	public void removeAudioNode(String name) {
		this.audioNodes.remove(name);
	}
	
	
//	/**
//	 * Adds a spatial to list
//	 */
//	public void addSpatial(Spatial spatial) {
//		this.spatials.put(spatial.getName(), spatial);
//	}
//	
//	/**
//	 * Remove a spatial from list
//	 * @param name
//	 */
//	public void removeSpatial(String name) {
//		this.spatials.remove(name);
//	}
	
	/**
	 * Register all events currently in this Control's eventMap with the EventManager
	 */
	public void registerEvents() {
		EventPortal.addListener(this, eventMap.keySet());
	}
	
	/**
	 * Remove this object as a listener for all its events
	 * Called when the object is inactive or just prior to deletion
	 */
	public void deleteEvents() {
		EventPortal.delListener(this, eventMap.keySet());				
	}
	
	/**
	 * Loads events the object listens for, strategies it uses to handle them(non-Javadoc)
	 * @see controls.IMasterControl#loadStrategy(eventManager.IEventListener)
	 */
	public void loadStrategy(IEventListener strategy) {
		Set<String> eventTypes = strategy.getEventTypes();
		
		for (String event: eventTypes) {
			eventMap.put(event, strategy);
//			EventPortal.addListener(this, event); //should do this via registerEvents(), not individually here
		}
	}
	
	/**
	 * Handles incoming events via the strategies in eventMap
	 */
	public void handleEvent(AppEvent event) {		
		if (eventMap.containsKey(event.getEventName())) {
			eventMap.get(event.getEventName()).handleEvent(event);
		}
	}
	
	/**
	 * Add another gameControl to the list of all potential controls for this object.
	 */
	public void addGameControl(GameControl control, String spatialName) {
//		this.controlList.add(control);
		Geometry testGeometry = geometries.get(spatialName);
		Node testNode = nodes.get(spatialName);
		AudioNode testAudio = audioNodes.get(spatialName);
		if (testGeometry!=null) {
			this.gameControls.put(control.getName(), control);
			control.setSpatial(testGeometry);
		}
		else if (testNode!=null) {
			this.gameControls.put(control.getName(), control);
			control.setSpatial(testNode);
		}
		else if (testAudio!=null) {
			this.gameControls.put(control.getName(), control);			
			control.setSpatial(testAudio);
		}
		else {
			//Log that target spatial does not exist
		}
	}
	
	/**
	 * Remove target GameControl from the list
	 */
	public void removeGameControl(String name) {
//		this.controlList.remove(gameControls.get(name));
		this.gameControls.remove(name);		
		//this.spatial.removeControl(clazz);
	}
	
	/**
	 * Inheriting classes list their specific IEventLister strategy types here
	 * and use loadStrategy() to add them to the object
	 */	
	public abstract void loadSpecificEventStrategies();
	
	public Set<String> getEventTypes() {
		return eventMap.keySet();
	}
	
	/**
	 * Sets the spatial that this MasterControl is attached to.
	 */
	public void setSpatial(Spatial spatial) {
        if (this.spatial != null && spatial != null) {
            throw new IllegalStateException("This control has already been added to a Spatial");
        }   
        this.spatial = spatial;
    }
    
	/**
	 * Get the currently attached spatial
	 * @return
	 */
    public Spatial getSpatial(){
        return spatial;
    }

    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }

    public boolean isEnabled() {
        return enabled;
    }
    
    public String getName() {
    	return this.name;
    }

    /**
     * To be implemented in subclass.
     */
    protected abstract void controlUpdate(float tpf);

    /**
     * To be implemented in subclass.
     */
    protected abstract void controlRender(RenderManager rm, ViewPort vp);

    public void update(float tpf) {
        if (!enabled)
            return;

        controlUpdate(tpf);
    }

    public void render(RenderManager rm, ViewPort vp) {
        if (!enabled)
            return;

        controlRender(rm, vp);
    }	
    
    
}
